package io.httpdoc.objc;

import io.httpdoc.core.Category;
import io.httpdoc.core.Constant;
import io.httpdoc.core.Property;
import io.httpdoc.core.Schema;
import io.httpdoc.core.exception.SchemaDesignException;
import io.httpdoc.core.modeler.Archetype;
import io.httpdoc.core.modeler.Modeler;
import io.httpdoc.core.supplier.Supplier;
import io.httpdoc.objc.core.ObjCSchema;
import io.httpdoc.objc.foundation.NSDictionary;
import io.httpdoc.objc.foundation.NSObject;
import io.httpdoc.objc.foundation.NSString;
import io.httpdoc.objc.fragment.*;
import io.httpdoc.objc.type.ObjCClass;
import io.httpdoc.objc.type.ObjCGenericType;
import io.httpdoc.objc.type.ObjCType;

import java.util.*;

/**
 * @author 杨昌沛 646742615@qq.com
 * @date 2018-07-25 16:37
 **/
public class ObjCMJExtensionModeler implements Modeler<ObjCFile> {

    /**
     * 设计
     *
     * @param archetype 原型
     * @return 模型
     * @throws SchemaDesignException Schema 不可设计的异常
     */
    @Override
    public Collection<ObjCFile> design(Archetype archetype) throws SchemaDesignException {
        final String comment = "Generated By Httpdoc";
        final String pkgGenerated = archetype.getPkg();
        final boolean pkgForced = archetype.isPkgForced();
        final Schema schema = archetype.getSchema();
        final String pkgTranslated = schema.getPkg();
        final String pkg = pkgForced || pkgTranslated == null ? pkgGenerated : pkgTranslated;
        final String name = schema.getName();
        final Supplier supplier = archetype.getSupplier();

        switch (schema.getCategory()) {
            case ENUM: {
                ObjCEnumHeaderFragment interfase = new ObjCEnumHeaderFragment();
                interfase.setName(name);
                interfase.setCommentFragment(new ObjCCommentFragment(schema.getDescription() != null ? schema.getDescription() + "\n" + comment : comment));
                Set<Constant> constants = schema.getConstants();
                for (Constant constant : (constants != null ? constants : Collections.<Constant>emptySet())) {
                    interfase.addExportFragment(constant.getDescription(), name + constant.getName());
                }

                ObjCEnumTargetFragment implementation = new ObjCEnumTargetFragment();
                implementation.setName(name);
                implementation.setCommentFragment(new ObjCCommentFragment(schema.getDescription() != null ? schema.getDescription() + "\n" + comment : comment));
                for (Constant constant : (constants != null ? constants : Collections.<Constant>emptySet())) {
                    implementation.addAssignFragment(constant.getDescription(), name + constant.getName(), constant.getName());
                }

                return Arrays.asList(
                        new ObjCFile(pkg, name, ObjCFile.Type.INTERFACE, interfase),
                        new ObjCFile(pkg, name, ObjCFile.Type.IMPLEMENTATION, implementation)
                );
            }
            case OBJECT: {
                ObjCClassHeaderFragment interfase = new ObjCClassHeaderFragment();
                interfase.setName(name);
                interfase.setCommentFragment(new ObjCCommentFragment(schema.getDescription() != null ? schema.getDescription() + "\n" + comment : comment));
                ObjCSchema superclass = (ObjCSchema) schema.getSuperclass();
                interfase.setSuperclass(superclass != null ? (ObjCClass) superclass.toObjCType(supplier) : ObjCType.valueOf(NSObject.class));
                Map<String, Property> properties = schema.getProperties();
                for (Map.Entry<String, Property> entry : (properties != null ? properties.entrySet() : Collections.<Map.Entry<String, Property>>emptySet())) {
                    Property property = entry.getValue();
                    ObjCPropertyFragment propertyFragment = new ObjCPropertyFragment();
                    String key = entry.getKey();
                    String alias = property.getAlias();
                    propertyFragment.setName(alias != null ? alias : key);
                    ObjCSchema type = (ObjCSchema) property.getType();
                    propertyFragment.setType(type.toObjCType(supplier));
                    propertyFragment.setCommentFragment(new ObjCCommentFragment(property.getDescription()));
                    interfase.addPropertyFragment(propertyFragment);
                }

                ObjCClassTargetFragment implementation = new ObjCClassTargetFragment();
                implementation.setName(name);
                implementation.setCommentFragment(new ObjCCommentFragment(schema.getDescription() != null ? schema.getDescription() + "\n" + comment : comment));

                ObjCSelectorFragment objectClassInArrayMethod = getObjectClassInArrayMethodFragment(properties);
                if (objectClassInArrayMethod != null) implementation.addSelectorFragment(objectClassInArrayMethod);

                ObjCSelectorFragment replacedKeyFromPropertyNameMethod = getReplacedKeyFromPropertyNameMethodFragment(properties);
                if (replacedKeyFromPropertyNameMethod != null) implementation.addSelectorFragment(replacedKeyFromPropertyNameMethod);

                return Arrays.asList(
                        new ObjCFile(pkg, name, ObjCFile.Type.INTERFACE, interfase),
                        new ObjCFile(pkg, name, ObjCFile.Type.IMPLEMENTATION, implementation)
                );
            }
            default:
                return Collections.emptySet();
        }
    }

    private ObjCSelectorFragment getReplacedKeyFromPropertyNameMethodFragment(Map<String, Property> properties) {
        ObjCSelectorFragment selector = new ObjCSelectorFragment();
        selector.setInstantial(false);
        selector.setName("mj_replacedKeyFromPropertyName");
        ObjCResultFragment objectClassInArrayResult = new ObjCResultFragment();
        objectClassInArrayResult.setType(new ObjCGenericType(ObjCType.valueOf(NSDictionary.class), ObjCType.valueOf(NSString.class), ObjCType.valueOf(NSString.class)));
        selector.setResultFragment(objectClassInArrayResult);
        ObjCBlockFragment block = new ObjCBlockFragment();
        block.getSentences().add("return @{");
        int count = 0;
        for (Map.Entry<String, Property> entry : (properties != null ? properties.entrySet() : Collections.<Map.Entry<String, Property>>emptySet())) {
            Property property = entry.getValue();
            String name = entry.getKey();
            String alias = property.getAlias();
            if (alias == null || alias.equals(name)) continue;
            String sentence = "    @\"" + alias + "\": @\"" + name + "\"" + (count++ > 0 ? "," : "");
            block.addSentence(1, sentence);
        }
        block.getSentences().add("};");
        selector.setBlockFragment(block);
        return count > 0 ? selector : null;
    }

    private ObjCSelectorFragment getObjectClassInArrayMethodFragment(Map<String, Property> properties) {
        ObjCSelectorFragment selector = new ObjCSelectorFragment();
        selector.setInstantial(false);
        selector.setName("mj_objectClassInArray");
        ObjCResultFragment objectClassInArrayResult = new ObjCResultFragment();
        objectClassInArrayResult.setType(new ObjCGenericType(ObjCType.valueOf(NSDictionary.class), ObjCType.valueOf(NSString.class), ObjCType.valueOf(NSString.class)));
        selector.setResultFragment(objectClassInArrayResult);
        ObjCBlockFragment block = new ObjCBlockFragment();
        block.getSentences().add("return @{");
        int count = 0;
        for (Map.Entry<String, Property> entry : (properties != null ? properties.entrySet() : Collections.<Map.Entry<String, Property>>emptySet())) {
            Property property = entry.getValue();
            Schema type = property.getType();
            Category category = type.getCategory();
            if (category == Category.ARRAY) {
                Schema component = type.getComponent();
                while (component.getCategory() == Category.ARRAY) component = component.getComponent();
                CharSequence className = component.getName();
                className = component.isPrimitive() ? "NSNumber" : className;
                String sentence = "    @\"" + entry.getKey() + "\": @\"" + className + "\"" + (count++ > 0 ? "," : "");
                block.getSentences().add(1, sentence);
            }
        }
        block.getSentences().add("};");
        selector.setBlockFragment(block);
        return count > 0 ? selector : null;
    }

}
